﻿// -----------------------------------------------------------------------
// <copyright file="DomainConfiguration.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Centido.Core
{
    using System;
    using System.Configuration;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using Configuration;

    /// <summary>
    ///   Domain configuration parameters used to initialize instances of the <see cref = "Centido.Core.Domain" /> class.
    /// </summary>
    /// <seealso cref = "Centido.Core.Domain" />
    /// <remarks>
    ///   Some of the parameters are required to build an instance of the <see cref = "Centido.Core.Domain" /> class.
    ///   They are the connection string and the domain provider name. If Oracle database is used, specifying the schema
    ///   name is also necessary. If parameter values are loaded from the configuration file,
    ///   then the domain name is used to find the corresponding domain parameters in the domain configuration section.
    /// </remarks>
    /// <example>
    ///   The following code creates an instance of the <see cref = "Centido.Core.DomainConfiguration" /> class
    ///   and builds a domain.
    ///   <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class DomainBuildSample
    ///     {
    ///         static void Main()
    ///         {
    ///             var config = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(config);
    ///         
    ///             Console.WriteLine(domain.DbName);
    ///         }
    ///     } 
    ///   </code>
    ///   The following example demonstrates how to load domain parameters from the configuration file.
    ///   <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class DomainBuildSample
    ///     {
    ///         static void Main()
    ///         {
    ///             var config = DomainConfiguration().Load("MyDomain");
    ///         
    ///             var domain = Domain.Build(config);
    ///         
    ///             Console.WriteLine(domain.DbName);
    ///         }
    ///     } 
    ///   </code>
    ///   And this is the corresponding content of the configuration file.
    ///   <code>
    ///     &lt;configuration&gt;
    ///         &lt;configSections&gt;
    ///             &lt;section name="Centido.Core.Configuration" type="Centido.Core.Configuration.CoreConfigurationSection, Centido.Core" /&gt;  
    ///         &lt;/configSections&gt;
    ///         &lt;Centido.Core.Configuration&gt;
    ///             &lt;domains&gt;
    ///                 &lt;add name="MyDomain" dbProvider="System.Data.SqlClient" dbConnection="Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;User Id=admin;Password=P@ssw0rd;"/&gt;
    ///             &lt;/domains&gt;
    ///         &lt;/Centido.Core.Configuration&gt;
    ///     &lt;/configuration&gt;
    ///   </code>
    /// </example   >
    public sealed class DomainConfiguration
    {
        /// <summary>
        ///   The default domain name.
        /// </summary>
        public const string DefaultDomainName = "Default";

        /// <summary>
        ///   The default database provider name.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
        public const string DefaultDbProviderName = "MSSQLSERVER";

        /// <summary>
        ///   The database connection string.
        /// </summary>
        private string _dbConnectionString = string.Empty;

        /// <summary>
        ///   The database provider name.
        /// </summary>
        private string _dbProviderName = DefaultDbProviderName;

        /// <summary>
        ///   The database schema name.
        /// </summary>
        private string _dbSchemaName = string.Empty;

        /// <summary>
        ///   The domain name.
        /// </summary>
        private string _domainName = DefaultDomainName;

        /// <summary>
        ///   The domain name.
        /// </summary>
        /// <remarks>
        ///   This property specifies the name of the item in the collection of domain parameters when loading from the configuration file.
        /// </remarks>
        /// <exception cref = "System.ArgumentNullException">Thrown when trying to set this property to null.</exception>
        /// <example    >
        ///   The following example shows how to load domain parameters from the configuration file specifying the domain name.
        ///   <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainBuildSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var config = DomainConfiguration().Load("MyDomain1");
        ///         
        ///             var domain = Domain.Build(config);
        ///         
        ///             Console.WriteLine(domain.DbName);
        ///         }
        ///     } 
        ///   </code>
        ///   And this is the corresponding content of the configuration file.
        ///   <code>
        ///     &lt;configuration&gt;
        ///         &lt;configSections&gt;
        ///             &lt;section name="Centido.Core.Configuration" type="Centido.Core.Configuration.CoreConfigurationSection, Centido.Core" /&gt;  
        ///         &lt;/configSections&gt;
        ///         &lt;Centido.Core.Configuration&gt;
        ///             &lt;domains&gt;
        ///                 &lt;add name="MyDomain1" dbProvider="System.Data.SqlClient" dbConnection="Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;User Id=admin;Password=P@ssw0rd;"/&gt;
        ///                 &lt;add name="MyDomain2" dbProvider="System.Data.SqlClient" dbConnection="Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;User Id=another_user;Password=P@ssw0rd;"/&gt;
        ///             &lt;/domains&gt; 
        ///         &lt;/Centido.Core.Configuration&gt;
        ///     &lt;/configuration&gt;
        ///   </code>
        /// </example   >
        public string DomainName
        {
            get { return _domainName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _domainName = value;
            }
        }

        /// <summary>
        ///   The database provider invariant name.
        /// </summary>
        /// <remarks>
        ///   This property specifies the invariant name of the database provider as registered in ADO.NET.
        /// </remarks>
        /// <exception cref = "System.ArgumentException">Thrown when trying to set this property to null.</exception>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
        public string DbProviderName
        {
            get { return _dbProviderName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _dbProviderName = value;
            }
        }

        /// <summary>
        ///   The database connection string.
        /// </summary>
        /// <remarks>
        ///   The database connection string format is specific to the database provider used.
        ///   A properly formatted connection string can be built using some of the connection string builders.
        /// </remarks>
        /// <example>
        ///   The following example shows how to build a connection string using one of the connection string builders.
        ///   <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainBuildSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var config = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = new SqlServerConnectionStringBuilder()
        ///                 {
        ///                     Host = @".\SQLEXPRESS",
        ///                     DbName = "MyDb",
        ///                     UserName = "admin",
        ///                     UserPassword = "P@ssw0rd"
        ///                 }.Build(),
        ///             DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(config);
        ///         
        ///             Console.WriteLine(domain.DbName);
        ///         }
        ///     }
        ///   </code>
        /// </example>
        /// <exception cref = "System.ArgumentException">Thrown when trying to set this property to null.</exception>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
        public string DbConnectionString
        {
            get { return _dbConnectionString; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _dbConnectionString = value;
            }
        }

        /// <summary>
        ///   The database schema name where the metadata was installed.
        /// </summary>
        /// <remarks>
        ///   This property is required to set when using Oracle database.
        /// </remarks>
        /// <exception cref = "System.ArgumentException">Thrown when trying to set this property to null.</exception>
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
        public string DbSchemaName
        {
            get { return _dbSchemaName; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                // Value can be empty

                _dbSchemaName = value;
            }
        }

        /// <summary>
        ///   Loads domain parameters from the configuration file searching for the specified domain name.
        /// </summary>
        /// <param name = "domainName">The specified domain name.</param>
        /// <returns>Returns an instance of the <see cref = "Centido.Core.DomainConfiguration" /> class with all parameters set
        ///   from the corresponding values in the configuration file.</returns>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "domainName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "domainName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when no domain with the specified name was found.</exception>
        public static DomainConfiguration Load(string domainName)
        {
            return Load(domainName, CoreConfigurationSection.DefaultSectionName);
        }

        /// <summary>
        ///   Loads domain parameters for the specified domain name from the configuration file section with the specified name.
        /// </summary>
        /// <param name = "domainName">The specified domain name.</param>
        /// <param name = "sectionName">The specified configuration section name.</param>
        /// <returns>Returns an instance of the <see cref = "Centido.Core.DomainConfiguration" /> class with all parameters set
        ///   from the corresponding values in the configuration file.</returns>
        /// <example    >
        ///   The following example shows how to load domain parameters from the configuration
        ///   file specifying the non-default section name.
        ///   <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class DomainBuildSample
        ///     {
        ///         static void Main()
        ///         {
        ///             var config = DomainConfiguration().Load("MyDomain", "ConfigSection");         
        ///             var domain = Domain.Build(config);
        ///         
        ///             Console.WriteLine(domain.DbName);
        ///         }
        ///     } 
        ///   </code>
        ///   And this is the corresponding configuration file.
        ///   <code>
        ///     &lt;configuration&gt;
        ///         &lt;configSections&gt;
        ///             &lt;section name="MyConfigSection" type="Centido.Core.Configuration.CoreConfigurationSection, Centido.Core" /&gt; 
        ///         &lt;/configSections&gt;
        ///         &lt;MyConfigSection&gt;
        ///             &lt;domains&gt;
        ///                 &lt;add name="MyDomain" dbProvider="System.Data.SqlClient" dbConnection="Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;User Id=admin;Password=P@ssw0rd;"/&gt;
        ///             &lt;/domains&gt;
        ///         &lt;/MyConfigSection&gt;
        ///     &lt;/configuration&gt;
        ///   </code>
        /// </example>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "domainName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when the value of <paramref name = "domainName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentNullException">Thrown when the value of <paramref name = "sectionName" /> is null.</exception>
        /// <exception cref = "System.ArgumentException">Throw when the value  of <paramref name = "sectionName" /> is an empty string.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when no domain with the specified name was found.</exception>
        /// <exception cref = "System.ArgumentException">Thrown when no section with the specified name was found in the configuration file.</exception>
        public static DomainConfiguration Load(
            string domainName = DefaultDomainName, string sectionName = CoreConfigurationSection.DefaultSectionName)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (sectionName.Trim().Length == 0)
                throw new ArgumentException("Section name cannot be empty.");
            if (domainName == null)
                throw new ArgumentNullException("domainName");
            if (domainName.Trim().Length == 0)
                throw new ArgumentException("Domain name cannot be empty.");

            domainName = domainName.Trim();
            sectionName = sectionName.Trim();

            var config = (CoreConfigurationSection)ConfigurationManager.GetSection(sectionName);

            // Searching for parameters of the specified domain
            var specifiedDomainParams = config.Domains.Cast<DomainElement>().Single(d => d.Name == domainName);

            return new DomainConfiguration
                       {
                           DomainName = specifiedDomainParams.Name,
                           DbProviderName = specifiedDomainParams.DbProviderName,
                           DbConnectionString = specifiedDomainParams.DbConnection,
                           DbSchemaName = specifiedDomainParams.DbSchema
                       };
        }

        /// <summary>
        ///   Creates a copy of domain parameters.
        /// </summary>
        /// <returns>Returns an instance of the <see cref = "Centido.Core.DomainConfiguration" /> class
        ///   that has all property values equal to the property values in the current instance.</returns>
        public DomainConfiguration CreateCopy()
        {
            var copy = new DomainConfiguration
                           {
                               DbConnectionString = DbConnectionString,
                               DbProviderName = DbProviderName,
                               DbSchemaName = DbSchemaName,
                               DomainName = DomainName
                           };

            return copy;
        }


        /// <summary>
        ///   Retrieves the corresponding connection string builder with parsed
        /// connection string parameters.
        /// </summary>
        /// <returns>Returns the corresponding connection string builder if there is one;
        /// otherwise, returns null.</returns>
        public ConnectionStringBuilder GetConnectionStringBuilder()
        {
            var builder = ConnectionStringBuilders.GetBuilder(DbProviderName);

            if (builder == null) return null;

            builder.Parse(DbConnectionString);

            return builder;
        }



        /// <summary>
        ///   Retrieves the corresponding LoBDb.NET metadata installer.
        /// </summary>
        /// <returns>Returns the corresponding LoBDb.NET metadata installer
        /// if there is one; otherwise, returns null.</returns>
        public Installer GetInstaller()
        {
            var builder = ConnectionStringBuilders.GetBuilder(DbProviderName);

            if (builder == null) return null;

            return Installers.GetInstaller(builder.ProductName);
        }
    }
}